using Mono.Cecil;
using Mono.Cecil.Cil;

namespace Cil.Ast;

public sealed class ArraySetItemExpression : SingleInstructionExpression
{
    public const int ArrayPos = 0;
    public const int IndexPos = 1;
    public const int ValuePos = 2;
    public const int MaxNodes = 3;

    public ArraySetItemExpression(Context context, Instruction position, Expression array, Expression exp_index, Expression exp_value)
        : base(context, position)
    {
        if (!(array.ReturnType is ArrayType))
        {
            throw new ArgumentException($"exp_array is not array ({array.ReturnType})");
        }

        var arrayItemType = ((array.ReturnType as ArrayType)!).ElementType;

        if (exp_index.ReturnType.MetadataType != MetadataType.Int32)
        {
            throw new ArgumentException($"exp_index should be Int16 ({exp_index.ReturnType})");
        }

        if (!arrayItemType.HardEquals(exp_value.ReturnType))
        {
            throw new ArgumentException($"exp_value should have same type as all other parameters types (exp_value: {exp_value.ReturnType}, others: {exp_index.ReturnType})");
        }

        // base class
        ReturnType = arrayItemType;
        ReadsStack = 2;
        WritesStack = 0;
        Nodes = new FixedList<Expression>(MaxNodes);

        ReturnType = TypeRef.Of(typeof(void), exp_value.MethodBody.Method.Module);

        // this
        ItemType = arrayItemType;
        ArrayExpression = array;
        IndexExpression = exp_index;
        ValueExpression = exp_value;
    }

    public TypeReference ItemType { get; private set; }

    public Expression ArrayExpression
    {
        get => Nodes[ArrayPos];
        set
        {
            Nodes[ArrayPos] = value;
            value.ParentNode = this;
        }
    }

    public Expression IndexExpression
    {
        get => Nodes[IndexPos];
        set
        {
            Nodes[IndexPos] = value;
            value.ParentNode = this;
        }
    }

    public Expression ValueExpression
    {
        get => Nodes[ValuePos];
        set
        {
            Nodes[ValuePos] = value;
            value.ParentNode = this;
        }
    }

    public override string ToString()
    {
        return string.Format("{0}[{1}] = {2}{3}", ArrayExpression, IndexExpression, ValueExpression, FinalString);
    }
}

static partial class IlAst
{
    public static ArraySetItemExpression ArraySetItem(Context context, Instruction position, Expression exp_array, Expression exp_index, Expression exp_value)
    {
        return new ArraySetItemExpression(context, position, exp_array, exp_index, exp_value);
    }
}